/***********************************************************************************************************************
* Copyright [2015-2017] Renesas Electronics Corporation and/or its licensors. All Rights Reserved.
*
* The contents of this file (the "contents") are proprietary and confidential to Renesas Electronics Corporation
* and/or its licensors ("Renesas") and subject to statutory and contractual protections.
*
* Unless otherwise expressly agreed in writing between Renesas and you: 1) you may not use, copy, modify, distribute,
* display, or perform the contents; 2) you may not use any name or mark of Renesas for advertising or publicity
* purposes or in connection with your use of the contents; 3) RENESAS MAKES NO WARRANTY OR REPRESENTATIONS ABOUT THE
* SUITABILITY OF THE CONTENTS FOR ANY PURPOSE; THE CONTENTS ARE PROVIDED "AS IS" WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTY, INCLUDING THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
* NON-INFRINGEMENT; AND 4) RENESAS SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL, OR CONSEQUENTIAL DAMAGES,
* INCLUDING DAMAGES RESULTING FROM LOSS OF USE, DATA, OR PROJECTS, WHETHER IN AN ACTION OF CONTRACT OR TORT, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE CONTENTS. Third-party contents included in this file may
* be subject to different terms.
* Author: K2 Power Inc.
**********************************************************************************************************************/

#include "hal_data.h"
#include "data_flash_helper.h"
//#include "common_utils.h"

static fsp_err_t int_storage_session_crc_calc(session_header_crc_t *p_session);

static volatile _Bool g_b_flash_event_not_blank = false;
static volatile _Bool g_b_flash_event_blank = false;
static volatile _Bool g_b_flash_event_erase_complete = false;
static volatile _Bool g_b_flash_event_write_complete = false;

/*******************************************************************************************************************//**
 * @brief This function is called to set the blank check event flags
 * @param[IN]   None
 * @retval      FSP_SUCCESS             Upon successful Flash_HP is blank
 * @retval      Any Other Error code    Upon unsuccessful Flash_HP is not blank
 **********************************************************************************************************************/
static fsp_err_t blankcheck_event_flag(void)
{
    fsp_err_t err = FSP_SUCCESS;
    /* Wait for callback function to set flag */
    while (!(g_b_flash_event_not_blank || g_b_flash_event_blank));

    if (g_b_flash_event_not_blank)
    {
//        APP_ERR_PRINT("\n\rFlash is not blank, not to write the data. Restart the application\n");
        /* Reset Flag */
        g_b_flash_event_not_blank = false;
        return (fsp_err_t)FLASH_EVENT_NOT_BLANK;
    }
    else
    {
//        APP_PRINT("\r\nFlash is blank\n");
        /* Reset Flag */
        g_b_flash_event_blank = false;
    }
    return err;
}

/*
 * Callback for internal data flash operation
 */
void flash_notification(flash_callback_args_t * p_args)
{
    if (FLASH_EVENT_NOT_BLANK == p_args->event)
    {
        g_b_flash_event_not_blank = true;
    }
    else if (FLASH_EVENT_BLANK == p_args->event)
    {
        g_b_flash_event_blank = true;
    }
    else if (FLASH_EVENT_ERASE_COMPLETE == p_args->event)
    {
        g_b_flash_event_erase_complete = true;
    }
    else if (FLASH_EVENT_WRITE_COMPLETE == p_args->event)
    {
        g_b_flash_event_write_complete = true;
    }
    else
    {
        /*No operation */
    }
}

/*
 * Initialise internal data flash storage operation
 */
fsp_err_t internal_storage_init(void)
{
    fsp_err_t status;

    status = (g_flash0.p_api->open(g_flash0.p_ctrl, g_flash0.p_cfg));
    return status;
}

#define CRC_SEED_FOR_SESSION_HEADER         (0x12345678)

session_header_crc_t session_header_of_flash;

/*
 * Read Internal data flash storage for particular info_type
 */
fsp_err_t internal_storage_read(uint8_t *data_buffer, unsigned int data_size,unsigned int info_type)
{
    unsigned int _dataflash_addr ;
    fsp_err_t ret = FSP_SUCCESS;

    switch(info_type)
    {
         case PERSISTENT_LOG_METADATA_TYPE:
              _dataflash_addr = LOG_METADATA_BLOCK_ADDR;
              break;
        default:
            return FSP_ERR_INVALID_ARGUMENT;
    }
    
    /* Check for Session header CRC */
    if (PERSISTENT_LOG_METADATA_TYPE == info_type)
    {
        /* Initialise structure */
        memset(&session_header_of_flash,0,sizeof(session_header_crc_t));
        // Added size of CRC as well
        data_size += 4;
        /* Considering data to be in a plain text, directly copy it to the data */
        memcpy((uint8_t *)&session_header_of_flash, (uint8_t *)_dataflash_addr, data_size);
    }
    else
    {
        /* Considering data to be in a plain text, directly copy it to the data */
        memcpy(data_buffer, (uint8_t *)_dataflash_addr, data_size);
    }

    if (FSP_SUCCESS == ret)
    {
        /* Check for Session header CRC */
        if (PERSISTENT_LOG_METADATA_TYPE == info_type)
        {
            uint32_t stored_crc = 0;

            /* Copy CRC value to local instance */
            stored_crc = session_header_of_flash.crc_value;

            /* Clear out CRC in structure */
            session_header_of_flash.crc_value = 0;

            /* Calculate CRC of flash session header */
            ret = int_storage_session_crc_calc(&session_header_of_flash);

            if (FSP_SUCCESS == ret)
            {
                /* Check whether CRC is matching */
                if (stored_crc == session_header_of_flash.crc_value)
                {
                    /* Copy session header to data buffer */
                    memcpy(data_buffer, &session_header_of_flash.session, sizeof(session_header_t));
                    return (FSP_SUCCESS);
                }
                else
                {
                    /* CRC Mismatch */
                    return (FSP_ERR_ABORTED);
                }
            }
        }
    }

    return (ret);
}

/*
 * Write data in internal data flash storage according to info_type
 */
fsp_err_t internal_storage_write(uint8_t *data_buffer , unsigned int data_size, unsigned int info_type)
{
    unsigned int _dataflash_addr ;
    unsigned int _num_of_blocks = 1;
    fsp_err_t ret_val = FSP_SUCCESS;
    uint8_t blk[DATA_FLASH_BLOCK_SIZE];
    uint32_t copy_size = 0;
    flash_result_t blank_check_result = FLASH_RESULT_BLANK;

    /* Get address of data flash according to info type */
    switch(info_type)
    {
         case PERSISTENT_LOG_METADATA_TYPE:
              /* Added CRC Checksum verification for Session Header */
              {
                  /* Blank ram storage for this structure */
                  memset(&session_header_of_flash,0,sizeof(session_header_of_flash));

                  /* Copy Session Header into local structure */
                  memcpy(&session_header_of_flash.session,data_buffer,data_size);

                  /* Calculate CRC of the Session header */
                  int_storage_session_crc_calc(&session_header_of_flash);

                  /* update pointer to session header with CRC */
                  data_buffer = (uint8_t *)&session_header_of_flash;
                  data_size = sizeof(session_header_of_flash);
              }

              _dataflash_addr = LOG_METADATA_BLOCK_ADDR;
              _num_of_blocks = NUM_OF_BLOCKS(LOG_BLOCK_LEN);
          break;
         break;
         
        default:
            return FSP_ERR_INVALID_ARGUMENT;
    }

    if( data_size > (_num_of_blocks * DATA_FLASH_BLOCK_SIZE))
    {
        return FSP_ERR_INVALID_ARGUMENT;
    }

    /* Erase flash memory location */
    if( FSP_SUCCESS == g_flash0.p_api->erase(g_flash0.p_ctrl, _dataflash_addr, _num_of_blocks))
    {
        /* wait for idle  */
        while (!g_b_flash_event_erase_complete);
        g_b_flash_event_erase_complete = false;

        /* Data flash blank check */
        ret_val = g_flash0.p_api->blankCheck(g_flash0.p_ctrl, _dataflash_addr, (DATA_FLASH_BLOCK_SIZE * _num_of_blocks), &blank_check_result);
        /* Error Handle */
        if (FSP_SUCCESS != ret_val)
        {
            return ret_val;
        }

        /* Validate the blank check result */
        if (FLASH_RESULT_NOT_BLANK == blank_check_result)
        {
            return (fsp_err_t)FLASH_RESULT_NOT_BLANK;
        }
        else if (FLASH_RESULT_BGO_ACTIVE == blank_check_result)
        {
            /* BlankCheck will update in Callback */
            /* Event flag will be updated in the blank check function when BGO is enabled */
            ret_val = blankcheck_event_flag();
            if(FSP_SUCCESS != ret_val)
            {
                return ret_val;
            }
        }

        while (data_size > 0)
        {
            memset(blk, 0, sizeof(blk));
            if (data_size > DATA_FLASH_BLOCK_SIZE)
            {
                copy_size = DATA_FLASH_BLOCK_SIZE;
            }
            else
            {
                copy_size = data_size;
            }
            memcpy(blk, data_buffer, copy_size);

            ret_val = g_flash0.p_api->write(g_flash0.p_ctrl, (uint32_t)blk, _dataflash_addr, DATA_FLASH_BLOCK_SIZE);
            if ( ret_val == FSP_SUCCESS)
            {
                /* wait for idle */
                while (!g_b_flash_event_write_complete);
                g_b_flash_event_write_complete = false;
                data_size -= (copy_size);
                data_buffer += (copy_size);
                _dataflash_addr += DATA_FLASH_BLOCK_SIZE;
            }
            else
            {
                ret_val = FSP_ERR_WRITE_FAILED;
                break;
            }
        }
    }
    else
    {
        /* Flash erase failure */
        ret_val = FSP_ERR_ERASE_FAILED;
    }

    return ret_val;
}

/*
 * Erase data in internal data flash storage according to info_type
 */
fsp_err_t internal_storage_erase(int_storage_type_t info_type)
{

    unsigned int _dataflash_addr ;
    unsigned int _num_of_blocks = 1;
    fsp_err_t ret_val = FSP_SUCCESS;
    uint8_t blk[DATA_FLASH_BLOCK_SIZE];
    flash_result_t blank_check_result = FLASH_RESULT_BLANK;

    /* Get address of data flash according to info type */
    switch(info_type)
    {
         case PERSISTENT_LOG_METADATA_TYPE:
             _dataflash_addr = LOG_METADATA_BLOCK_ADDR;
             _num_of_blocks = NUM_OF_BLOCKS(LOG_BLOCK_LEN);
         break;
         default:
            return FSP_ERR_INVALID_ARGUMENT;
    }

    /* Erase flash memory location */
    if( FSP_SUCCESS == g_flash0.p_api->erase(g_flash0.p_ctrl, _dataflash_addr, _num_of_blocks))
    {
        /* wait for idle  */
        while (!g_b_flash_event_erase_complete);
        g_b_flash_event_erase_complete = false;

        /* Data flash blank check */
        ret_val = g_flash0.p_api->blankCheck(g_flash0.p_ctrl, _dataflash_addr, (DATA_FLASH_BLOCK_SIZE * _num_of_blocks), &blank_check_result);
        /* Error Handle */
        if (FSP_SUCCESS != ret_val)
        {
            return ret_val;
        }

        /* Validate the blank check result */
        if (FLASH_RESULT_NOT_BLANK == blank_check_result)
        {
            return (fsp_err_t)FLASH_RESULT_NOT_BLANK;
        }
        else if (FLASH_RESULT_BGO_ACTIVE == blank_check_result)
        {
            /* BlankCheck will update in Callback */
            /* Event flag will be updated in the blank check function when BGO is enabled */
            ret_val = blankcheck_event_flag();
            if(FSP_SUCCESS != ret_val)
            {
                return ret_val;
            }
        }

        while (_num_of_blocks > 0)
        {
            memset(blk, 0, sizeof(blk));
            ret_val = g_flash0.p_api->write(g_flash0.p_ctrl, (uint32_t)blk, _dataflash_addr, DATA_FLASH_BLOCK_SIZE);
            if ( ret_val == FSP_SUCCESS)
            {
                /* wait for idle */
                while (!g_b_flash_event_write_complete);
                g_b_flash_event_write_complete = false;
                _dataflash_addr += DATA_FLASH_BLOCK_SIZE;
            }
            else
            {
                ret_val = FSP_ERR_WRITE_FAILED;
                break;
            }
            _num_of_blocks--;
        }
    }
    else
    {
        /* Flash erase failure */
        ret_val = FSP_ERR_ERASE_FAILED;
    }

    return ret_val;
}

/*
 * Validate CRC Checksum of Session Header
 * */

static fsp_err_t int_storage_session_crc_calc(session_header_crc_t *p_session)
{
    fsp_err_t status = FSP_SUCCESS;
    uint32_t calc_crc = 0;
    crc_input_t crc_input;

    /* Open CRC Module */
    status = R_CRC_Open(&g_crc0_ctrl, &g_crc0_cfg);
    if (FSP_SUCCESS != status)
    {
        return status;
    }

    /* Calculate CRC of session header */
    crc_input.crc_seed = (uint32_t)CRC_SEED_FOR_SESSION_HEADER;
    crc_input.num_bytes = (uint32_t) sizeof(session_header_t);
    crc_input.p_input_buffer = &(p_session->session);
    status = R_CRC_Calculate(&g_crc0_ctrl, &crc_input, &calc_crc);
    if (FSP_SUCCESS == status)
    {
        /* copy calculated CRC value */
        p_session->crc_value = calc_crc;
    }

    /* Close CRC Module */
    status = R_CRC_Close(&g_crc0_ctrl);

    return status;
}

/*
 * Reinitialise internal data flash
 */
fsp_err_t internal_storage_deinit(void)
{
    return (g_flash0.p_api->close(g_flash0.p_ctrl));
}
